﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Diagnostics.Contracts;

namespace RaisingForce.Raise.Hardware
{
    /// <summary>
    /// Представляет интерфейс доступа к оборудованию робота.
    /// </summary>
    /// <para>Это абстрактный класс по умолчанию для интерфейса <see cref="IRobotHardware"/>.</para>
    [ContractClass(typeof(RobotHardwareContract))]
    public abstract class RobotHardware :
        IRobotHardware
    {
        #region Constructors

        /// <summary>
        /// Создает экземпляр класса <see cref="RobotHardware"/>.
        /// </summary>
        protected RobotHardware()
        {
            // does nothing
        }

        #endregion

        #region Private Declarations

        private IRobotEngine engine;

        private IRobotSensor sensor;

        /// <summary>
        /// Synchronization object for IRobotHardware interface.
        /// </summary>
        private readonly object hardwareSyncObject = new object();

        [ContractInvariantMethod]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
        private void ObjectInvariant()
        {
            Contract.Invariant(this.hardwareSyncObject != null);
        }

        private void actionWithInitializedHardware(
            Action action,
            string failureMessage)
        {
            Contract.Requires(action != null);
            Contract.Requires(failureMessage != null);
            if (this.engine == null || this.sensor == null)
            {
                throw new RobotHardwareException(
                    RobotHardwareFailureReason.ActionNotSupported,
                    RobotHardwareExceptionMessages.RobotWasNotInitialized);
            }
            try
            {
                action();
            }
            catch (RobotHardwareException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new RobotHardwareException(
                    RobotHardwareFailureReason.Hardware,
                    failureMessage,
                    innerException: ex);
            }

        }

        #endregion

        #region Protected Abstract Declarations

        /// <summary>
        /// Выполняет проверку наличия совместимого для работы с роботом оборудования 
        /// и инициализацию начального состояния данного интерфейса.
        /// <para>Этот метод нужно переопределить в классах-наследниках для
        /// задания логики поведения открытому методу <see cref="Initialize"/>.</para>
        /// </summary>
        /// <param name="options">Параметры, используемые для настройки действий робота.</param>
        /// <param name="hardwareSyncObject">Объект синхронизации для доступа
        /// к открытым членам интерфейса <see cref="IRobotHardware"/>.</param>
        /// <param name="engine">Интерфейс доступа к двигателю робота.</param>
        /// <param name="sensor">Интерфейс доступа к ультразвуковому датчику робота.</param>
        protected abstract void InitializeHardware(
            IRobotHardwareOptions options,
            object hardwareSyncObject,
            out IRobotEngine engine,
            out IRobotSensor sensor);

        /// <summary>
        /// Устанавливает соединение с роботом через Bluetooth или другой интерфейс.
        /// <para>Этот метод нужно переопределить в классах-наследниках для
        /// задания логики поведения открытому методу <see cref="Connect"/>.</para>
        /// </summary>
        protected abstract void ConnectHardware();

        /// <summary>
        /// Прерывает ранее установленное соединение с роботом.
        /// <para>Этот метод нужно переопределить в классах-наследниках для
        /// задания логики поведения открытому методу <see cref="Disconnect"/>.</para>
        /// </summary>
        protected abstract void DisconnectHardware();

        #endregion

        #region IRobotHardware Members

        /// <summary>
        /// Получает имя интерфейса, дружественное для пользователя.
        /// <para>Это свойство нужно переопределить в классах-наследниках для
        /// задания отличного от <c>null</c> значения.</para>
        /// </summary>
        /// <value>Имя интерфейса, дружественное для пользователя.
        /// Если это значение равно <c>null</c> или пустой строке, то для отображения используется
        /// информация, полученная из атрибута <see cref="RobotHardwareAttribute"/>.</value>
        /// <remarks>
        /// Это свойство нужно переопределить в классах-наследниках для
        /// задания отличного от <c>null</c> значения.
        /// </remarks>
        public virtual string Name
        {
            get { return null; }
        }

        /// <summary>
        /// Получает описание интерфейса, дружественное для пользователя.
        /// </summary>
        /// <value>Описание интерфейса, дружественное для пользователя.
        /// Если это значение равно <c>null</c> или пустой строке, то для отображения используется
        /// информация, полученная из атрибута <see cref="RobotHardwareAttribute"/>.</value>
        /// <remarks>
        /// Это свойство нужно переопределить в классах-наследниках для
        /// задания отличного от <c>null</c> значения.
        /// </remarks>
        public virtual string Description
        {
            get { return null; }
        }

        /// <summary>
        /// Получает интерфейс доступа к двигателю робота.
        /// </summary>
        /// <value>Интерфейс доступа к двигателю робота.
        /// Это значение не может быть равно null.</value>
        /// <seealso cref="IRobotHardware"/>
        public IRobotEngine Engine
        {
            get
            {
                return this.engine ?? RobotInvalidHardware.Engine;
            }
        }

        /// <summary>
        /// Получает интерфейс доступа к ультразвуковому датчику робота.
        /// </summary>
        /// <value>Интерфейс доступа к ультразвуковому датчику робота.
        /// Это значение не может быть равно null.</value>
        /// <seealso cref="IRobotHardware"/>
        public IRobotSensor Sensor
        {
            get
            {
                return this.sensor ?? RobotInvalidHardware.Sensor;
            }
        }

        /// <summary>
        /// Выполняет проверку наличия совместимого для работы с роботом оборудования 
        /// и инициализацию начального состояния данного интерфейса.
        /// </summary>
        /// <param name="options">Параметры, используемые для настройки действий робота.</param>
        /// <exception cref="RobotHardwareException">
        /// Совместимое для работы с роботом оборудование не найдено,
        /// либо инициализация начального состояния невозможна.
        /// </exception>
        /// <seealso cref="IRobotHardware"/>
        /// <seealso cref="RobotHardwareException"/>
        public void Initialize(IRobotHardwareOptions options)
        {
            lock (this.hardwareSyncObject)
            {
                if (this.engine != null || this.sensor != null)
                {
                    throw new RobotHardwareException(
                        RobotHardwareFailureReason.ActionNotSupported,
                        RobotHardwareExceptionMessages.RobotWasInitialized);
                }
                IRobotEngine engine;
                IRobotSensor sensor;
                try
                {
                    this.InitializeHardware(
                        options,
                        this.hardwareSyncObject,
                        out engine,
                        out sensor);
                }
                catch (RobotHardwareException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    throw new RobotHardwareException(
                        RobotHardwareFailureReason.Hardware,
                        RobotHardwareExceptionMessages.SoftwareInitializationError,
                        innerException: ex);
                }
                if (engine == null || sensor == null)
                {
                    throw new RobotHardwareException(
                        RobotHardwareFailureReason.Hardware,
                        RobotHardwareExceptionMessages.SoftwareInitializationError);
                }
                this.engine = engine;
                this.sensor = sensor;
            }
        }

        /// <summary>
        /// Устанавливает соединение с роботом через Bluetooth или другой интерфейс.
        /// </summary>
        /// <exception cref="RobotHardwareException">
        /// Соединение с роботом установить невозможно.
        /// </exception>
        /// <seealso cref="IRobotHardware"/>
        /// <seealso cref="RobotHardwareException"/>
        public void Connect()
        {
            lock (this.hardwareSyncObject)
            {
                this.actionWithInitializedHardware(
                    this.ConnectHardware,
                    RobotHardwareExceptionMessages.ConnectionFailure);
            }
        }

        /// <summary>
        /// Прерывает ранее установленное соединение с роботом.
        /// </summary>
        /// <exception cref="RobotHardwareException">
        /// Ошибка при попытке прервать соединение.
        /// </exception>
        /// <seealso cref="IRobotHardware"/>
        /// <seealso cref="RobotHardwareException"/>
        public void Disconnect()
        {
            lock (this.hardwareSyncObject)
            {
                this.actionWithInitializedHardware(
                    this.DisconnectHardware,
                    RobotHardwareExceptionMessages.DisconnectionFailure);
            }
        }

        #endregion
    }
}
